package oa.hleast.xswl.controller.oauth;

import cn.binarywang.wx.miniapp.api.WxMaService;
import cn.binarywang.wx.miniapp.bean.WxMaJscode2SessionResult;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONObject;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiImplicitParams;
import io.swagger.annotations.ApiOperation;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import me.chanjar.weixin.common.error.WxErrorException;
import oa.hleast.common.constant.AuthConstant;
import oa.hleast.common.constant.GlobalConstants;
import oa.hleast.common.result.Result;
import oa.hleast.common.utils.JsonUtil;
import oa.hleast.common.web.exception.BizException;
import oa.hleast.common.web.util.RequestUtils;
import oa.hleast.common.wx.config.WeAppConfig;
import oa.hleast.xswl.common.config.AppProps;
import oa.hleast.xswl.controller.oauth.zlb.UserBeanDto;
import oa.hleast.xswl.controller.oauth.zlb.ZLBAuth;
import oa.hleast.xswl.pojo.domain.UmsMember;
import oa.hleast.xswl.pojo.dto.AuthMemberDTO;
import oa.hleast.xswl.service.ISysCommonConfigService;
import oa.hleast.xswl.service.IUmsUserService;
import oa.hleast.xswl.utils.DateUtil;
import oa.hleast.xswl.utils.RSAUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.client.fluent.Request;
import org.apache.http.entity.ContentType;
import org.apache.logging.log4j.util.Strings;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.oauth2.common.OAuth2AccessToken;
import org.springframework.security.oauth2.provider.endpoint.TokenEndpoint;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import springfox.documentation.annotations.ApiIgnore;

import java.nio.charset.Charset;
import java.security.Principal;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

@Api(tags = "认证中心1")
@RestController
@RequestMapping("/oauth")
@AllArgsConstructor
@Slf4j
public class AuthController {

    private final TokenEndpoint tokenEndpoint;
    private final ISysCommonConfigService commonConfigService;

    @ApiOperation(value = "OAuth2认证", notes = "login")
    @ApiImplicitParams({
            @ApiImplicitParam(name = "grant_type", defaultValue = "password", value = "授权模式", required = true, paramType = "query", dataType = "String", dataTypeClass = String.class),
            @ApiImplicitParam(name = "client_id", defaultValue = "client", value = "Oauth2客户端ID", required = true, paramType = "query", dataType = "String", dataTypeClass = String.class),
            @ApiImplicitParam(name = "client_secret", defaultValue = "123456", value = "Oauth2客户端秘钥", required = true, paramType = "query", dataType = "String", dataTypeClass = String.class),
            @ApiImplicitParam(name = "refresh_token", value = "刷新token", paramType = "query", dataType = "String", dataTypeClass = String.class),
            @ApiImplicitParam(name = "username", defaultValue = "admin", value = "登录用户名", paramType = "query", dataType = "String", dataTypeClass = String.class),
            @ApiImplicitParam(name = "password", defaultValue = "123456", value = "登录密码", paramType = "query", dataType = "String", dataTypeClass = String.class),

            // 微信小程序认证参数（无小程序可忽略）
            @ApiImplicitParam(name = "code", value = "小程序code", paramType = "query", dataType = "String", dataTypeClass = String.class),
            @ApiImplicitParam(name = "encryptedData", value = "包括敏感数据在内的完整用户信息的加密数据", paramType = "query", dataType = "String", dataTypeClass = String.class),
            @ApiImplicitParam(name = "iv", value = "加密算法的初始向量", paramType = "query", dataType = "String", dataTypeClass = String.class),
    })
    @PostMapping("/token")
    public Result<OAuth2AccessToken> postAccessToken(
            @ApiIgnore Principal principal,
            @ApiIgnore @RequestParam Map<String, String> parameters
    ) throws HttpRequestMethodNotSupportedException {

        log.info("parameters = {}", parameters);
        OAuth2AccessToken oAuth2AccessToken;

        /**
         * 获取登录认证的客户端ID
         *
         * 兼容两种方式获取Oauth2客户端信息（client_id、client_secret）
         * 方式一：client_id、client_secret放在请求路径中
         * 方式二：放在请求头（Request Headers）中的Authorization字段，且经过加密，例如 Basic Y2xpZW50OnNlY3JldA== 明文等于 client:secret
         * Y2l0eS1icmFpbi1hcHA6MTIzNDU2 city-brain-app 城市大脑app
         * Basic empmd3ctYXBwOjEyMzQ1Ng== zjfww-app 浙里办app  -文旅红
         * Basic empmd3ctd2x5eS1hcHA6MTIzNDU2   zjfww-wlyy-app 浙里办app -文旅预约
         */
        String clientId = RequestUtils.getAuthClientId();
        log.info("clientId = {}", clientId);
        switch (clientId) {
            case AuthConstant.MP_CLIENT_ID:
                // 微信认证
                oAuth2AccessToken = this.handleForWxAuth(principal, parameters);
                break;
            case AuthConstant.MP_CLIENT_CITY_BRAIN_APP:
                //城市大脑认证
                oAuth2AccessToken = this.handleForCityBrainApp(principal, parameters);
                break;
            case AuthConstant.MP_ZJFWW_APP:
                //浙里办 -文旅红
                oAuth2AccessToken = this.handleForZJFWWApp(AuthConstant.MP_ZJFWW_APP, principal, parameters);
                break;
            case AuthConstant.MP_ZJFWW_WLYY_APP:
                //浙里办 -文旅预约
                oAuth2AccessToken = this.handleForZJFWWApp(AuthConstant.MP_ZJFWW_WLYY_APP, principal, parameters);
                break;
            default:
                log.info("账户密码登录");
                oAuth2AccessToken = tokenEndpoint.postAccessToken(principal, parameters).getBody();
                break;
        }
        return Result.success(oAuth2AccessToken);
    }

    private final ZLBAuth zlbAuth;

    private OAuth2AccessToken handleForZJFWWApp(String type, Principal principal, Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {

        String ticket = parameters.get("code");
        parameters.put("grant_type", parameters.get("grantType"));
        log.info("ticket = {},parameters = {}", ticket, parameters);
        JSONObject request = new JSONObject();

        UserBeanDto userInfo = null;
        String zjfwUserId = "-1";
        if (!"8888".equals(ticket)) {
            log.info("ticket = {},time  = {}", ticket, System.currentTimeMillis());
            String userInfoResult = zlbAuth.getUserInfo(type, zlbAuth.ticketValidation(type, ticket));
            userInfo = JsonUtil.string2Obj(userInfoResult, UserBeanDto.class);
            zjfwUserId = userInfo.getUserid();
        } else {
            zjfwUserId = "8afac0cc6d192767016d1dde1e8b3863";
            userInfo = UserBeanDto.builder().username("马汝业").mobile("18354685917").idnum("370781199411176513").build();
        }

        AuthMemberDTO authMemberDTO = umsUserService.getUserByZjfwwUserId(zjfwUserId);

        // 微信授权登录 会员信息不存在时 注册会员
        if (ObjectUtil.isNull(authMemberDTO)) {
            UmsMember user = new UmsMember()
                    .setZjfwUserId(zjfwUserId)
                    .setAvatar(AuthConstant.DEFAULT_AVATAR)
                    .setNickname(userInfo.getUsername())
                    .setUsername(userInfo.getUsername())
                    .setFullname(userInfo.getUsername())
                    .setMobile(userInfo.getMobile())
                    .setIdCardNum(userInfo.getIdnum())
                    .setAuthStatus(2)
                    // 加密密码移除前缀加密方式 {bcrypt}
                    .setPassword(passwordEncoder.encode(zjfwUserId).replace(AuthConstant.BCRYPT, Strings.EMPTY))
                    .setStatus(GlobalConstants.STATUS_NORMAL_VALUE);

            log.info("user = {}", user);

            boolean res = umsUserService.save(user);
            if (!res) {
                throw new BizException("注册会员失败");
            }
        }


        // oauth2认证参数对应授权登录时注册会员的username、password信息，模拟通过oauth2的密码模式认证
        parameters.put("username", zjfwUserId);
        parameters.put("password", zjfwUserId);

        OAuth2AccessToken oAuth2AccessToken = tokenEndpoint.postAccessToken(principal, parameters).getBody();
        log.info("oAuth2AccessToken  = {}", oAuth2AccessToken);

        return oAuth2AccessToken;
    }


    private final WeAppConfig weAppConfig;
    private final IUmsUserService umsUserService;
    private final PasswordEncoder passwordEncoder;
    private final AppProps appProps;


    private OAuth2AccessToken handleForCityBrainApp(Principal principal, Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {
        String authCode = parameters.get("code");
        log.info("authCode = {},time = {}", authCode, System.currentTimeMillis());
        final JSONObject accessToken;
        try {
            accessToken = this.getAccessToken(authCode);
        } catch (Exception e) {
            e.printStackTrace();
            log.info("e = {}", e, e);
            throw new BizException("系统错误");
        }
        String mixedUserId = accessToken.getString("mixedUserId");
        String accessTokenVal = accessToken.getString("accessToken");

        final JSONObject userInfo;
        try {
            userInfo = this.getUserInfo(accessTokenVal, mixedUserId);
        } catch (Exception e) {
            e.printStackTrace();
            log.info("e = {}", e, e);
            throw new BizException("系统错误");
        }

        String custUserId = userInfo.getString("custUserId");
        AuthMemberDTO authMemberDTO = umsUserService.getUserByCustUserId(custUserId);

        // 微信授权登录 会员信息不存在时 注册会员
        if (ObjectUtil.isNull(authMemberDTO)) {
            UmsMember user = new UmsMember()
                    .setCustUserId(custUserId)
                    .setAvatar(AuthConstant.DEFAULT_AVATAR)
                    .setNickname(userInfo.getString("custName"))
                    .setUsername(userInfo.getString("custName"))
                    .setFullname(userInfo.getString("custName"))
                    .setMobile(userInfo.getString("mobile"))
                    .setIdCardNum(userInfo.getString("certNo"))
                    .setAuthStatus(2)
                    // 加密密码移除前缀加密方式 {bcrypt}
                    .setPassword(passwordEncoder.encode(custUserId).replace(AuthConstant.BCRYPT, Strings.EMPTY))
                    .setStatus(GlobalConstants.STATUS_NORMAL_VALUE);

            log.info("user = {}", user);

            boolean res = umsUserService.save(user);
            if (!res) {
                throw new BizException("注册会员失败");
            }
        }


        // oauth2认证参数对应授权登录时注册会员的username、password信息，模拟通过oauth2的密码模式认证
        parameters.put("username", custUserId);
        parameters.put("password", custUserId);

        OAuth2AccessToken oAuth2AccessToken = tokenEndpoint.postAccessToken(principal, parameters).getBody();
        return oAuth2AccessToken;
    }

    public OAuth2AccessToken handleForWxAuth(Principal principal, Map<String, String> parameters) throws HttpRequestMethodNotSupportedException {
        WxMaService wxService = weAppConfig.getWxMaService(weAppConfig.getAppid());
        String code = parameters.get("code");

        if (StrUtil.isBlank(code)) {
            throw new BizException("code不能为空");
        }

        String openid = code;
        String sessionKey = code;
        String unionid = code;
        WxMaJscode2SessionResult session = null;
        try {
            if (code.equals("888")) {
                openid = code;
                sessionKey = code;
                unionid = code;
            } else {
                session = wxService.getUserService().getSessionInfo(code);
                openid = session.getOpenid();
                sessionKey = session.getSessionKey();
                unionid = session.getUnionid();
            }
        } catch (WxErrorException e) {
            e.printStackTrace();
            log.info("{}", e.getMessage(), e);
            throw new BizException("获取微信信息异常");
        }
        log.info("openid ={},sessionKey = {},unionid = {}", openid, sessionKey, unionid);

        AuthMemberDTO authMemberDTO = umsUserService.getUserByOpenid(openid);

        // 微信授权登录 会员信息不存在时 注册会员
        if (ObjectUtil.isNull(authMemberDTO)) {
            UmsMember user = new UmsMember()
                    .setOpenid(openid)
                    .setSessionKey(sessionKey)
                    .setUnionid(unionid)
                    .setAvatar(AuthConstant.DEFAULT_AVATAR)
                    .setNickname("微信用户")
                    .setUsername(openid)
                    // 加密密码移除前缀加密方式 {bcrypt}
                    .setPassword(passwordEncoder.encode(openid).replace(AuthConstant.BCRYPT, Strings.EMPTY))
                    .setStatus(GlobalConstants.STATUS_NORMAL_VALUE);

            if("true".equals(commonConfigService.getByConfigKey("balance_switch").getValue())){
                user.setPoint(10*1000L);
            }
            log.info("user = {}", user);

            boolean res = umsUserService.save(user);
            if (!res) {
                throw new BizException("注册会员失败");
            }
        }

        // oauth2认证参数对应授权登录时注册会员的username、password信息，模拟通过oauth2的密码模式认证
        parameters.put("username", openid);
        parameters.put("password", openid);

        OAuth2AccessToken oAuth2AccessToken = tokenEndpoint.postAccessToken(principal, parameters).getBody();
        return oAuth2AccessToken;
    }

    private JSONObject getAccessToken(String authCode) throws Exception {
        String localUrl = appProps.getCityBrainHttpUrl() + "/api/auth/getAccessToken";

        Map<String, Object> dataMap = new HashMap<>();
        dataMap.put("authCode", authCode);

        return getResult(localUrl, dataMap);
    }

    private JSONObject getResult(String localUrl, Map<String, Object> dataMap) throws Exception {
        String appId = appProps.getCityBrainAppId();
        Map outMap = geneOutMap(appId, dataMap);

        log.info("localUrl = {} ,outMap = {}", localUrl, outMap);
        String resp = Request.Post(localUrl).connectTimeout(5000).socketTimeout(5000).bodyString(JSONObject.toJSONString(outMap), ContentType.APPLICATION_JSON).
                execute().returnContent().asString(Charset.forName("UTF-8"));

        log.info("resp = {}", resp);

        if (StringUtils.isEmpty(resp)) {
            throw new BizException("调用网络接口失败");
        }

        String errorCode = JSONObject.parseObject(resp).getString("errorCode");
        if ("0".equals(errorCode)) {
            JSONObject result = JSONObject.parseObject(resp).getJSONObject("result");
            log.info("result = {}", result);
            return result;
        } else {
            throw new RuntimeException("调用失败" + JSONObject.parseObject(resp).getJSONObject("value"));
        }
    }

    private Map<String, String> geneOutMap(String appId, Map<String, Object> data) throws Exception {
        Map<String, String> outMap = new HashMap<>();
        outMap.put("appId", appId);
        outMap.put("timestamp", DateUtil.getFormate(new Date()));
        String dataStr = JSONObject.toJSONString(data);
        outMap.put("data", dataStr);
        String systemId = appProps.getCityBrainSystemId();
        outMap.put("systemId", systemId);
        String str = String.format("appId=%s&data=%s&systemId=%s&timestamp=%s", outMap.get("appId"), outMap.get("data"), outMap.get("systemId"), outMap.get("timestamp"));
//        String privateKey = "MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQCkNT+y5DSrrToyyZBXZOk2G8K4oVc22wr1wHs+J3idauTFm4T3II0qNaE37za03XaM7267N9OQjA6e6MbyKakHqbBeVBE4GwyeddXmDaW4uTiTUbkq6RdWwcgXhHfx/Ym8Lx6CJjxastgS9uBBMu7sNNTJz2Mz88GwP70XQJyxkscwyoL7IeO3dussKwAWmE9Qlv3S1wNLFzWdVP2U1pesIBDMw2baODvoG4dxEJGUmPF4uIMVklR5RQiMayOtx0X7Mkab4Euh/Y1qunoeEnYBlyoex6nAcCd3iJwXaCoj1NB5fOMDZZmHNNj5k98SEYuuznhdfT0k4j32WZRo1cFRAgMBAAECggEAIIen+dF5mbT0fZEuthF4nc58EcVsmKJmFm8uOw7otw6xCXCYs7hQHteG+6S5CrWAwcTBA/e4Qr9sOhDVMK4OHUWBef5fduTs6Ath93Qmq2SOD6k17IlGpVo4QGhutpLKkWwoOBJJDJK3eXJM1L25VTPWH7Ck72rPqRHHbO7Q2+BtrsczswbhELAjjTY8lUgaOgePHrLRlMwiesES6SG9IUx+HYJ+li6BJaxkY80+TlocMnAhoNXcW410qiFjwJFpyCoYuTYjulZANqnd+c9pae3upJ4kWIdbjU4S4U6KVwMmnjc6uRr6wHjmB1qCqjF66yKjokBKS33i6d+zZOqgAQKBgQDTAr1NC5vs7jumtKOe40hoo3xOod7FFpcVXx/kAe2tSWX0a8RsymPVvWRODJaR30WD0sxk8FEVGW9dZkb3GmaDcUM6rhhOVvZVI5Nf5+ZBWWgP7/KYkB8o3fXtLeluNaLoeXCn7LDfppw7ogZ5mZqZuuDmgKIT+BqZi/QpPut+AQKBgQDHN/PN6w474wTNJVQJrmsaHjstQ4GDzDygHfUaAOyJ3WaTrF3CgKyuLH/awvQDJbDrsfGjNDEJFRE7jYCic4etMUOPgGhbnXNDnhaV8vIwziJuldL6TXIjmQIvd2/z1w5Sh6HkcNfd1Y+X9Di/CTZYsRKa2ziTC7B3KtUn35jjUQKBgQCGeLd0sxJCUqwXGAUxy3womw8r+UJDbZsI1p83KfdyuFTOCCOchPpMiy6yApmuCpB9asgxB2lUvenXpsdQ0DVGTNbgPXRl8Et5hHt14g491SFZDrwWd7ozzAfeh6S8RFg2OLYPfepWDRKBekcQFVCN6adLnewsN6+T7LnD00I2AQKBgEwrxpp7P2wCdzmBBkrVV7oayQGbiA++rYeDLM4OgGKONsRL6WWPEXdfKFuaovX5GVZlKUD/maghV/9vMTnr9npwFabur/gnkYTPE0+uHpAuoNerjYmKxxN5U26WukV/eshvd6UCyGbXWxn8lR3mBdOPkM+I9m6yz3wg8nNWePyhAoGBAM1PrqdlzUnYDBVkW6N2dRdEq0VpDWCx47cqYgFZN2Nn55FuqEI+9fpgnnaJgsAuuOUdxS3aXuDhMGiPFIPcLdcrFEeSzd2kw8WNNb0K+GqCwPTSdL3E9j0LIK10tuB8YpBnOaP+9F60DAQqDOoIw5vvU/otHvvm2OWf3HOXlkUt";
        String privateKey = appProps.getCityBrainPrivateKey();

        String sign = RSAUtils.sign(str.getBytes(), privateKey);
        outMap.put("sign", sign);
        return outMap;
    }

    private JSONObject getUserInfo(String accessToken, String mixedUserId) throws Exception {
        String localUrl = appProps.getCityBrainHttpUrl() + "/api/auth/getUserInfoByAccessToken";

        Map<String, Object> dataMap = new HashMap<>();
        dataMap.put("accessToken", accessToken);
        dataMap.put("mixedUserId", mixedUserId);

        return getResult(localUrl, dataMap);
    }

}
